package references

import (
	"jvm/instructions/base"
	"jvm/rtda"
	"jvm/rtda/heap"
	"reflect"
)

type ATHROW struct {
	base.NoOperandsInstruction
}

func (a *ATHROW) Execute(jFrame *rtda.JFrame) {
	ex := jFrame.OperandStack().PopRef()
	if ex == nil {
		panic("java.lang.NullPointerException")
	}
	jThread := jFrame.JThread()
	if !findAndGotoExceptionHandler(jThread, ex) {
		handleUncaughtException(jThread, ex)
	}
}

func handleUncaughtException(jThread *rtda.JThread, ex *heap.Object) {
	jThread.ClearStack()

	jMsg := ex.GetRefVar("detailMessage", "Ljava/lang/String;")
	goMsg := heap.GoString(jMsg)
	println(ex.Class().JavaName() + ": " + goMsg)

	stes := reflect.ValueOf(ex.Extra())
	for i := 0; i < stes.Len(); i++ {
		ste := stes.Index(i).Interface().(interface {
			String() string
		})
		println("\tat" + ste.String())
	}
}

func findAndGotoExceptionHandler(jThread *rtda.JThread, ex *heap.Object) bool {
	for {
		jFrame := jThread.CurrentJFrame()
		pc := jFrame.NextPC() - 1

		handlerPC := jFrame.Method().FindExceptionHandler(ex.Class(), pc)
		if handlerPC > 0 {
			stack := jFrame.OperandStack()
			stack.Clear()
			stack.PushRef(ex)
			jFrame.SetNextPC(handlerPC)
			return true
		}
		jThread.PopJFrame()
		if jThread.IsStackEmpty() {
			break
		}
	}
	return false
}
